package com.ftqh.security.oauth2.provider.client;

import com.ftqh.security.oauth2.provider.client.enums.RefreshTokenValidity;
import com.ftqh.security.oauth2.provider.client.enums.RefreshTokenValidityConverter;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.util.*;

/**
 * \* Created with IntelliJ IDEA.
 * \* User: Administrator
 * \* Date: 2017/1/5
 * \* Time: 15:33
 * \* To change this template use File | Settings | File Templates.
 * \* Description:
 * \
 */
@Entity
@Table(name="sys_security_oauth_client_details")
@EntityListeners(AuditingEntityListener.class)
public class OAuth2ClientDetails implements ClientDetails {
    private static final long serialVersionUID = 2474911229696850298L;
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(
            name = "uuid",
            strategy = "org.hibernate.id.UUIDGenerator",
            parameters = {
                    @Parameter(
                            name = "uuid_gen_strategy_class",
                            value = "org.hibernate.id.uuid.CustomVersionOneStrategy"
                    )
            }
    )
    private String clientId;
    private String clientName;
    private String clientSecret;
    private String scope;
    private String resourceIds;
    private String authorizedGrantTypes;
    private String webServerRedirectUri;
    private String authorities;
    private Integer accessTokenValidity = 60 * 60 * 2;  //2小时
    @Convert( converter = RefreshTokenValidityConverter.class )
    private RefreshTokenValidity refreshTokenValidity = RefreshTokenValidity.SEVEN_DAY;
    private String additionalInformation;
    private String autoApprove;

    @Transient
    private Map<String, Object> additionalInfos = new LinkedHashMap<String, Object>();

    @CreatedDate
    private Long createdAt;

    @LastModifiedDate
    private Long lastModified;

    @Override
    public String getClientId() {
        return clientId;
    }

    @Override
    public Set<String> getResourceIds() {
        if (StringUtils.hasText(resourceIds)) {
            Set<String> resources = StringUtils
                    .commaDelimitedListToSet(resourceIds);
            return resources;
        }
        return Collections.emptySet();
    }

    @Override
    public boolean isSecretRequired() {
        return this.clientSecret != null;
    }

    @Override
    public String getClientSecret() {
        return clientSecret;
    }

    @Override
    public boolean isScoped() {
        return !StringUtils.isEmpty(scope);
    }

    @Override
    public Set<String> getScope() {
        if (StringUtils.hasText(scope)) {
            Set<String> scopes = StringUtils.commaDelimitedListToSet(scope);
            return scopes;
        }
        return Collections.emptySet();
    }

    @Override
    public Set<String> getAuthorizedGrantTypes() {
        if (StringUtils.hasText(authorizedGrantTypes)) {
            return StringUtils.commaDelimitedListToSet(authorizedGrantTypes);
        } else {
            return new HashSet<String>(Arrays.asList(
                    "authorization_code", "refresh_token"));
        }
    }

    @Override
    public Set<String> getRegisteredRedirectUri() {
        if (StringUtils.hasText(webServerRedirectUri)) {
            return StringUtils.commaDelimitedListToSet(webServerRedirectUri);
        }
        return null;
    }

    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        if (StringUtils.hasText(authorities)) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList(authorities);
        }
        return Collections.emptyList();
    }

    @Override
    public Integer getAccessTokenValiditySeconds() {
        return accessTokenValidity;
    }

    @Override
    public Integer getRefreshTokenValiditySeconds() {
        return refreshTokenValidity.getSeconds();
    }

    @Override
    public boolean isAutoApprove(String scope) {
        if(!StringUtils.isEmpty(autoApprove)){
            Set<String> autoApproveScopes = StringUtils.commaDelimitedListToSet(autoApprove);
            for (String auto : autoApproveScopes) {
                if (auto.equals("true") || scope.matches(auto)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public Map<String, Object> getAdditionalInformation() {
        JsonMapper mapper = createJsonMapper();
        if(additionalInfos.isEmpty()){
            try {
                additionalInfos = mapper.read(additionalInformation, Map.class);
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        return Collections.unmodifiableMap(additionalInfos);
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getClientName() {
        return clientName;
    }

    public void setClientName(String clientName) {
        this.clientName = clientName;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public void setResourceIds(String resourceIds) {
        this.resourceIds = resourceIds;
    }

    public void setAuthorizedGrantTypes(String authorizedGrantTypes) {
        this.authorizedGrantTypes = authorizedGrantTypes;
    }

    public String getWebServerRedirectUri() {
        return webServerRedirectUri;
    }

    public void setWebServerRedirectUri(String webServerRedirectUri) {
        this.webServerRedirectUri = webServerRedirectUri;
    }

    public void setAuthorities(String authorities) {
        this.authorities = authorities;
    }

    public Integer getAccessTokenValidity() {
        return accessTokenValidity;
    }

    public void setAccessTokenValidity(Integer accessTokenValidity) {
        this.accessTokenValidity = accessTokenValidity;
    }

    public RefreshTokenValidity getRefreshTokenValidity() {
        return refreshTokenValidity;
    }

    public void setRefreshTokenValidity(RefreshTokenValidity refreshTokenValidity) {
        this.refreshTokenValidity = refreshTokenValidity;
    }
    public void setRefreshTokenValidity(Integer refreshTokenValidity) {
        this.refreshTokenValidity = RefreshTokenValidity.fromSeconds(refreshTokenValidity);
    }
    public void setAdditionalInformation(String additionalInformation) {
        this.additionalInformation = additionalInformation;
    }

    public Long getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Long createdAt) {
        this.createdAt = createdAt;
    }

    public Long getLastModified() {
        return lastModified;
    }

    public void setLastModified(Long lastModified) {
        this.lastModified = lastModified;
    }

    public String getAutoApprove() {
        return autoApprove;
    }

    public void setAutoApprove(String autoApprove) {
        this.autoApprove = autoApprove;
    }

    interface JsonMapper {
        String write(Object input) throws Exception;

        <T> T read(String input, Class<T> type) throws Exception;
    }

    private static JsonMapper createJsonMapper() {
        if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", null)) {
            return new JacksonMapper();
        }
        else if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
            return new Jackson2Mapper();
        }
        return new NotSupportedJsonMapper();
    }

    private static class JacksonMapper implements JsonMapper {
        private org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper();

        @Override
        public String write(Object input) throws Exception {
            return mapper.writeValueAsString(input);
        }

        @Override
        public <T> T read(String input, Class<T> type) throws Exception {
            return mapper.readValue(input, type);
        }
    }

    private static class Jackson2Mapper implements JsonMapper {
        private com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();

        @Override
        public String write(Object input) throws Exception {
            return mapper.writeValueAsString(input);
        }

        @Override
        public <T> T read(String input, Class<T> type) throws Exception {
            return mapper.readValue(input, type);
        }
    }

    private static class NotSupportedJsonMapper implements JsonMapper {
        @Override
        public String write(Object input) throws Exception {
            throw new UnsupportedOperationException(
                    "Neither Jackson 1 nor 2 is available so JSON conversion cannot be done");
        }

        @Override
        public <T> T read(String input, Class<T> type) throws Exception {
            throw new UnsupportedOperationException(
                    "Neither Jackson 1 nor 2 is available so JSON conversion cannot be done");
        }
    }
    @Override
    public boolean equals(Object obj) {

        if (null == obj) {
            return false;
        }

        if (this == obj) {
            return true;
        }

        if (!getClass().equals(ClassUtils.getUserClass(obj))) {
            return false;
        }

        OAuth2ClientDetails that = (OAuth2ClientDetails) obj;

        return null == this.getClientId() ? false : this.getClientId().equals(that.getClientId());
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {

        int hashCode = 17;

        hashCode += null == getClientId() ? 0 : getClientId().hashCode() * 31;

        return hashCode;
    }

    @Override
    public String toString() {
        return "OAuth2ClientDetails{" +
                "clientId='" + clientId + '\'' +
                ", clientName='" + clientName + '\'' +
                ", clientSecret='" + clientSecret + '\'' +
                ", scope='" + scope + '\'' +
                ", resourceIds='" + resourceIds + '\'' +
                ", authorizedGrantTypes='" + authorizedGrantTypes + '\'' +
                ", webServerRedirectUri='" + webServerRedirectUri + '\'' +
                ", authorities='" + authorities + '\'' +
                ", accessTokenValidity=" + accessTokenValidity +
                ", refreshTokenValidity=" + refreshTokenValidity +
                ", additionalInformation='" + additionalInformation + '\'' +
                ", createdAt=" + createdAt +
                ", lastModified=" + lastModified +
                '}';
    }
}
